#include "ntpTime.h"
#include <time.h>

NTPTime::NTPTime() {
    _lastUpdate = 0;
    _hour = 0;
    _minute = 0;
    _second = 0;
    _timeValid = false;
    _timezoneOffset = 8;  // default back to local (UTC+8)
}

bool NTPTime::begin() {
        // Configure NTP with timezone offset (seconds) and China-friendly servers
        configTime(_timezoneOffset * 3600, 0, "ntp1.aliyun.com", "ntp2.aliyun.com", "cn.pool.ntp.org");
    return true;
}

bool NTPTime::attemptSyncIfNeeded(unsigned long &lastNTPAttempt, unsigned long intervalMs) {
    unsigned long now = millis();
    // When time isn't valid yet, try more often (cap the interval at 30s)
    unsigned long desiredInterval = _timeValid ? intervalMs : (intervalMs < 30000 ? intervalMs : 30000);
    if (now - lastNTPAttempt < desiredInterval) return false; // not time yet
    lastNTPAttempt = now;
    bool ok = updateTime();
    if (!ok && !_timeValid) {
        // If still invalid, schedule another try sooner (~5s)
        unsigned long since = millis() - lastNTPAttempt;
        if (since > 5000) {
            // already waited long enough; next loop will hit again
        } else {
            lastNTPAttempt = millis() - (desiredInterval - 5000);
        }
    }
    return ok;
}

bool NTPTime::updateTime() {
    struct tm timeinfo;
    if (!getLocalTime(&timeinfo, 8000)) {  // Reduced timeout for faster failure detection
        Serial.printf("NTP sync failed - WiFi:%d IP:%s GW:%s DNS:%s RSSI:%ddBm\n",
                      (int)WiFi.status(),
                      WiFi.localIP().toString().c_str(),
                      WiFi.gatewayIP().toString().c_str(),
                      WiFi.dnsIP(0).toString().c_str(),
                      WiFi.RSSI());
        
        // Quick retry with server reconfiguration
        configTime(_timezoneOffset * 3600, 0, "ntp1.aliyun.com", "ntp2.aliyun.com", "cn.pool.ntp.org");
        delay(300); // Reduced delay
        if (!getLocalTime(&timeinfo, 5000)) { // Faster second attempt
            Serial.println("NTP retry failed - will try again later");
            _timeValid = false;
            return false;
        } else {
            Serial.println("NTP retry succeeded");
        }
    }
    // Cache time components for efficient access
    _hour = timeinfo.tm_hour;
    _minute = timeinfo.tm_min;
    _second = timeinfo.tm_sec;
    _lastUpdate = millis();
    _lastEpoch = mktime(&timeinfo); // Store epoch for timezone calculations
    _timeValid = true;

    Serial.printf("NTP sync successful: %02d:%02d:%02d (UTC%+d)\n", 
                  _hour, _minute, _second, _timezoneOffset);
    return true;
}

uint8_t NTPTime::getHour() {
    if (_timeValid) {
        // 基于最后一次同步时间计算当前时间
        unsigned long elapsedSeconds = (millis() - _lastUpdate) / 1000;
        unsigned long totalSeconds = _hour * 3600 + _minute * 60 + _second + elapsedSeconds;
        return (totalSeconds / 3600) % 24;
    }
    return 0;
}

uint8_t NTPTime::getMinute() {
    if (_timeValid) {
        unsigned long elapsedSeconds = (millis() - _lastUpdate) / 1000;
        unsigned long totalSeconds = _hour * 3600 + _minute * 60 + _second + elapsedSeconds;
        return (totalSeconds / 60) % 60;
    }
    return 0;
}

uint8_t NTPTime::getSecond() {
    if (_timeValid) {
        unsigned long elapsedSeconds = (millis() - _lastUpdate) / 1000;
        unsigned long totalSeconds = _hour * 3600 + _minute * 60 + _second + elapsedSeconds;
        return totalSeconds % 60;
    }
    return 0;
}

String NTPTime::getFormattedTime() {
    if (!_timeValid) {
        return "Time not synced";
    }

    uint8_t hour = getHour();
    uint8_t minute = getMinute();
    uint8_t second = getSecond();

    char buffer[20];
    snprintf(buffer, sizeof(buffer), "%02d:%02d:%02d", hour, minute, second);
    return String(buffer);
}

bool NTPTime::isTimeValid() {
    return _timeValid;
}

void NTPTime::setTimezone(int offset) {
    _timezoneOffset = offset;
    // 重新配置时区
    if (WiFi.status() == WL_CONNECTED) {
        // Use China-friendly servers as well when timezone changes
        configTime(_timezoneOffset * 3600, 0, "ntp1.aliyun.com", "ntp2.aliyun.com", "cn.pool.ntp.org");
    }
}

bool NTPTime::getTimeForOffset(int tzOffsetHours, uint8_t &hour, uint8_t &minute, uint8_t &second, uint8_t &wday, int &year, int &month, int &day) {
    if (!_timeValid) return false;
    // compute elapsed seconds since last epoch sample
    unsigned long elapsed = (millis() - _lastUpdate) / 1000;
    time_t nowEpoch = _lastEpoch + elapsed;
    // apply requested timezone offset (hours) to get local time for that tz
    nowEpoch += tzOffsetHours * 3600;
    struct tm t;
    if (!gmtime_r(&nowEpoch, &t)) return false;
    hour = t.tm_hour;
    minute = t.tm_min;
    second = t.tm_sec;
    wday = t.tm_wday; // 0=Sunday
    year = t.tm_year + 1900;
    month = t.tm_mon + 1;
    day = t.tm_mday;
    return true;
}

// Sakamoto's algorithm to compute weekday: 0=Sunday..6=Saturday
static int weekday_of(int year, int month, int day) {
    static int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
    if (month < 3) year -= 1;
    return (year + year/4 - year/100 + year/400 + t[month-1] + day) % 7;
}

// Helper: compute the day (1-31) of the nth weekday (0=Sunday) in a month/year
static int nth_weekday_of_month(int year, int month, int weekday, int nth) {
    int firstWeekday = weekday_of(year, month, 1);
    int delta = (weekday - firstWeekday + 7) % 7;
    int day = 1 + delta + (nth - 1) * 7;
    return day;
}

bool NTPTime::getPacificTime(uint8_t &hour, uint8_t &minute, uint8_t &second, uint8_t &wday, int &year, int &month, int &day) {
    if (!_timeValid) return false;
    unsigned long elapsed = (millis() - _lastUpdate) / 1000;
    time_t nowEpoch = _lastEpoch + elapsed;
    // convert to UTC struct tm
    // Compute Pacific-local baseline assuming PST (-8) first
    time_t pacPstEpoch = nowEpoch + (-8) * 3600;
    struct tm pst;
    gmtime_r(&pacPstEpoch, &pst);
    int pYear = pst.tm_year + 1900;
    int pMonth = pst.tm_mon + 1;
    int pDay = pst.tm_mday;
    int pHour = pst.tm_hour;
    int pMin = pst.tm_min;

    // Determine DST boundaries in Pacific-local time
    int dstStartDay = nth_weekday_of_month(pYear, 3, 0, 2); // 2nd Sunday in March
    int dstEndDay = nth_weekday_of_month(pYear, 11, 0, 1);  // 1st Sunday in Nov

    // Compare pac-local date/time to boundaries (local times at 02:00)
    // Build comparable integer values YYYYMMDDHHMM for comparison
    auto cmpVal = [](int yy, int mm, int dd, int hh, int mn) {
        return ((yy * 100 + mm) * 100 + dd) * 10000 + hh * 100 + mn;
    };
    int nowVal = cmpVal(pYear, pMonth, pDay, pHour, pMin);
    int startVal = cmpVal(pYear, 3, dstStartDay, 2, 0);
    int endVal = cmpVal(pYear, 11, dstEndDay, 2, 0);

    bool isDst = false;
    if (startVal < endVal) {
        if (nowVal >= startVal && nowVal < endVal) isDst = true;
    } else {
        if (!(nowVal >= endVal && nowVal < startVal)) isDst = true;
    }

    int offset = isDst ? -7 : -8;
    time_t pacEpoch = nowEpoch + offset * 3600;
    struct tm pt;
    gmtime_r(&pacEpoch, &pt);
    hour = pt.tm_hour; minute = pt.tm_min; second = pt.tm_sec; wday = pt.tm_wday;
    year = pt.tm_year + 1900; month = pt.tm_mon + 1; day = pt.tm_mday;
    return true;
}

const char* NTPTime::getPacificTimeZoneAbbrev() {
    if (!_timeValid) return "PST"; // default
    unsigned long elapsed = (millis() - _lastUpdate) / 1000;
    time_t nowEpoch = _lastEpoch + elapsed;
    // Compute Pacific-local baseline assuming PST (-8) first
    time_t pacPstEpoch = nowEpoch + (-8) * 3600;
    struct tm pst;
    gmtime_r(&pacPstEpoch, &pst);
    int pYear = pst.tm_year + 1900;
    int pMonth = pst.tm_mon + 1;
    int pDay = pst.tm_mday;
    int pHour = pst.tm_hour;
    int pMin = pst.tm_min;

    // Determine DST boundaries in Pacific-local time
    int dstStartDay = nth_weekday_of_month(pYear, 3, 0, 2); // 2nd Sunday in March
    int dstEndDay = nth_weekday_of_month(pYear, 11, 0, 1);  // 1st Sunday in Nov

    // Compare pac-local date/time to boundaries (local times at 02:00)
    // Build comparable integer values YYYYMMDDHHMM for comparison
    auto cmpVal = [](int yy, int mm, int dd, int hh, int mn) {
        return ((yy * 100 + mm) * 100 + dd) * 10000 + hh * 100 + mn;
    };
    int nowVal = cmpVal(pYear, pMonth, pDay, pHour, pMin);
    int startVal = cmpVal(pYear, 3, dstStartDay, 2, 0);
    int endVal = cmpVal(pYear, 11, dstEndDay, 2, 0);

    bool isDst = false;
    if (startVal < endVal) {
        if (nowVal >= startVal && nowVal < endVal) isDst = true;
    } else {
        if (!(nowVal >= endVal && nowVal < startVal)) isDst = true;
    }

    return isDst ? "PDT" : "PST";
}